//////////////////////////////////////////////////////////////////////////
// This file contains only the support definitions for CZipDir class
// implementation. This it to unload the ZipDir.h from secondary stuff.
#ifndef _ZIP_DIR_DEFINITIONS
#define _ZIP_DIR_DEFINITIONS

#if defined(LINUX)
	#include <ctype.h>
#endif

#include <ISystem.h>
#include "Endian.h"
#include <IMemory.h>
#include <ProjectDefines.h>





namespace ZipDir
{

	struct CZipFile
	{
		FILE * m_file;
		int64 m_nSize;
		int64 m_nCursor;
		const char *m_szFilename;
		ICustomMemoryBlock* m_pInMemoryData;


		CZipFile() : m_file(0), m_nSize(0), m_nCursor(0),m_pInMemoryData(0),m_szFilename(0) {}

		CZipFile& operator=( const CZipFile &from )
		{
			m_file = from.m_file;
			m_nSize = from.m_nSize;
			m_nCursor = from.m_nCursor;
			m_szFilename = from.m_szFilename;
			m_pInMemoryData = from.m_pInMemoryData;
			if (m_pInMemoryData)
				m_pInMemoryData->AddRef();
			return *this;
		}
		

		bool IsInMemory() const { return m_pInMemoryData != 0; }
		void LoadToMemory(IMemoryBlock* pData = 0);
		void UnloadFromMemory();
		void Close( bool bUnloadFromMem=true );
	
	private:
		explicit CZipFile( const CZipFile &file ) {}; // Protect copy ctor.
	};

// possible errors occuring during the method execution
// to avoid clushing with the global Windows defines, we prefix these with ZD_
enum ErrorEnum
{
	ZD_ERROR_SUCCESS = 0,
	ZD_ERROR_IO_FAILED,
	ZD_ERROR_UNEXPECTED,
	ZD_ERROR_UNSUPPORTED,
	ZD_ERROR_INVALID_SIGNATURE,
	ZD_ERROR_ZIP_FILE_IS_CORRUPT,
	ZD_ERROR_DATA_IS_CORRUPT,
	ZD_ERROR_NO_CDR,
	ZD_ERROR_CDR_IS_CORRUPT,
	ZD_ERROR_NO_MEMORY,
	ZD_ERROR_VALIDATION_FAILED,
	ZD_ERROR_CRC32_CHECK,
	ZD_ERROR_ZLIB_FAILED,
	ZD_ERROR_ZLIB_CORRUPTED_DATA,
	ZD_ERROR_ZLIB_NO_MEMORY,
	ZD_ERROR_CORRUPTED_DATA,
	ZD_ERROR_INVALID_CALL,
	ZD_ERROR_NOT_IMPLEMENTED,
	ZD_ERROR_FILE_NOT_FOUND,
	ZD_ERROR_DIR_NOT_FOUND,
	ZD_ERROR_NAME_TOO_LONG,
	ZD_ERROR_INVALID_PATH,
	ZD_ERROR_FILE_ALREADY_EXISTS
};

// encryption settings for zip header - stored in m_encryptedHeaders var
enum
{
	HEADERS_NOT_ENCRYPTED=0,
	HEADERS_STREAMCIPHER=1,
	HEADERS_ENCRYPTED=2
};

// the error describes the reason of the error, as well as the error code, line of code where it happened etc.
struct Error
{
	Error(ErrorEnum _nError, const char* _szDescription, const char* _szFunction, const char* _szFile, unsigned _nLine):
		nError(_nError),
		m_szDescription(_szDescription),
		szFunction(_szFunction),
		szFile(_szFile),
		nLine(_nLine)
	{
	}

	ErrorEnum nError;
	const char* getError();

	const char* getDescription() {return m_szDescription;}
	const char* szFunction, *szFile;
	unsigned nLine;
protected:
	// the description of the error; if needed, will be made as a dynamic string
	const char* m_szDescription;
};

//#define THROW_ZIPDIR_ERROR(ZD_ERR,DESC) throw Error (ZD_ERR, DESC, __FUNCTION__, __FILE__, __LINE__)



#define THROW_ZIPDIR_ERROR(ZD_ERR,DESC) CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,DESC )


// possible initialization methods
enum InitMethodEnum
{
	// initialize as fast as possible, with minimal validation
	ZD_INIT_FAST,
	// after initialization, scan through all file headers, precache the actual file data offset values and validate the headers
	ZD_INIT_FULL,
	// scan all file headers and try to decompress the data, searching for corrupted files
	ZD_INIT_VALIDATE_IN_MEMORY,
	// store archive in memory
	ZD_INIT_VALIDATE,
	// maximum level of validation, checks for integrity of the archive
	ZD_INIT_VALIDATE_MAX = ZD_INIT_VALIDATE
};

typedef void* (*FnAlloc) (void* pUserData, unsigned nItems, unsigned nSize);
typedef void (*FnFree)  (void* pUserData, void* pAddress);

// instance of this class just releases the memory when it's destructed
template <class _Heap>
struct TSmartHeapPtr
{
	TSmartHeapPtr(_Heap* pHeap):
		m_pHeap(pHeap),
		m_pAddress(NULL)
	{
	}
	~TSmartHeapPtr()
	{
		Release();
	}

	void Attach (void*p)
	{
		Release();
		m_pAddress = p;
	}

	void* Detach()
	{
		void* p = m_pAddress;
		m_pAddress = NULL;
		return p;
	}

	void Release()
	{
		if (m_pAddress)
		{
			m_pHeap->FreeTemporary(m_pAddress);
			m_pAddress = NULL;
		}
	}
protected:
	// the pointer to free
	void* m_pAddress;
	_Heap* m_pHeap;
};

typedef TSmartHeapPtr<CMTSafeHeap> SmartPtr;

// Uncompresses raw (without wrapping) data that is compressed with method 8 (deflated) in the Zip file
// returns one of the Z_* errors (Z_OK upon success)
extern int ZipRawUncompress (CMTSafeHeap*pHeap, void* pUncompressed, unsigned long* pDestSize, const void* pCompressed, unsigned long nSrcSize);

// compresses the raw data into raw data. The buffer for compressed data itself with the heap passed. Uses method 8 (deflate)
// returns one of the Z_* errors (Z_OK upon success), and the size in *pDestSize. the pCompressed buffer must be at least nSrcSize*1.001+12 size
extern int ZipRawCompress (CMTSafeHeap*pHeap, const void* pUncompressed, unsigned long* pDestSize, void* pCompressed, unsigned long nSrcSize, int nLevel);

// fseek wrapper with memory in file support.
extern int64 FSeek(CZipFile *zipFile, int64 origin, int command, void *pInternalFile);

// fread wrapper with file in memory  support
extern int64 FRead(CZipFile *zipFile, void *data, size_t nElemSize, size_t nCount,void *pInternalFile);

// ftell wrapper with file in memory support
extern int64 FTell(CZipFile *zipFile);

extern uint32 FileNameHash( const char *filename );


//////////////////////////////////////////////////////////////////////////
struct SExtraZipFileData
{
	SExtraZipFileData() : nLastModifyTime(0) {}

	uint64 nLastModifyTime;
};

#ifdef OPTIMIZED_READONLY_ZIP_ENTRY
// this is the record about the file in the Zip file.
struct FileEntry
{
	enum {INVALID_DATA_OFFSET = 0xFFFFFFFF};

	struct SOptimizedDataDescriptor
	{
		uint32 lSizeCompressed;    // compressed size                 4 bytes
		uint32 lSizeUncompressed;  // uncompressed size               4 bytes
		uint32 lCRC32;						 // crc-32													4 bytes
	};
	SOptimizedDataDescriptor desc;
	uint32 nFileDataOffset;		// offset of the packed info inside the file; NOTE: this can be INVALID_DATA_OFFSET, if not calculated yet!
	uint32 nNameOffset;			  // offset of the file name in the name pool for the directory

	uint16 nMethod;					// the method of compression (0 if no compression/store)


	FileEntry() {}
	FileEntry(const ZipFile::CDRFileHeader& header,const SExtraZipFileData &extra)
	{
		desc.lSizeCompressed = header.desc.lSizeCompressed;
		desc.lSizeUncompressed = header.desc.lSizeUncompressed;
		desc.lCRC32 = header.desc.lCRC32;
		nFileDataOffset = header.lLocalHeaderOffset + sizeof (ZipFile::LocalFileHeader) + header.nFileNameLength;
		nMethod           = header.nMethod;
		nNameOffset       = 0; // we don't know yet
	}

	bool IsInitialized() { return false; }

	// returns the name of this file, given the pointer to the name pool
	const char* GetName(const char* pNamePool)const
	{
		return pNamePool + nNameOffset;
	}
	// Not used in Read-Only version.
	void OnNewFileData(void* pUncompressed, unsigned nSize, unsigned nCompressedSize, unsigned nCompressionMethod, bool bContinuous) {};
	uint64 GetModificationTime() { return 0; };
	void GetMemoryUsage( ICrySizer *pSizer ) const { /* nothing */ }
};
#else //OPTIMIZED_READONLY_ZIP_ENTRY
// this is the record about the file in the Zip file.
struct FileEntry
{
	enum {INVALID_DATA_OFFSET = 0xFFFFFFFF};

	ZipFile::DataDescriptor desc;
	uint32 nFileDataOffset;		// offset of the packed info inside the file; NOTE: this can be INVALID_DATA_OFFSET, if not calculated yet!
	uint32 nFileHeaderOffset; // offset of the local file header
	uint32 nNameOffset;			  // offset of the file name in the name pool for the directory

	uint16 nMethod;					// the method of compression (0 if no compression/store)
	uint16 nReserved0;				// Reserved

	// the file modification times
	uint16 nLastModTime;
	uint16 nLastModDate;
	uint64 nNTFS_LastModifyTime;

	// the offset to the start of the next file's header - this
	// can be used to calculate the available space in zip file
	uint32 nEOFOffset;

	FileEntry():nFileHeaderOffset(INVALID_DATA_OFFSET){}
	FileEntry(const ZipFile::CDRFileHeader& header,const SExtraZipFileData &extra);

	bool IsInitialized ()
	{
		// structure marked as non-initialized should have nFileHeaderOffset == INVALID_DATA_OFFSET
		return nFileHeaderOffset != INVALID_DATA_OFFSET;
	}
	// returns the name of this file, given the pointer to the name pool
	const char* GetName(const char* pNamePool)const
	{
		return pNamePool + nNameOffset;
	}

	// sets the current time to modification time
	// calculates CRC32 for the new data
	void OnNewFileData(void* pUncompressed, unsigned nSize, unsigned nCompressedSize, unsigned nCompressionMethod, bool bContinuous);

	uint64 GetModificationTime();

	void GetMemoryUsage( ICrySizer *pSizer ) const { /* nothing */ }
};
#endif //OPTIMIZED_READONLY_ZIP_ENTRY


// tries to refresh the file entry from the given file (reads fromthere if needed)
// returns the error code if the operation was impossible to complete
extern ErrorEnum Refresh (CZipFile*f, FileEntry* pFileEntry);

// writes into the file local header (NOT including the name, only the header structure)
// the file must be opened both for reading and writing
extern ErrorEnum UpdateLocalHeader (FILE*f, FileEntry* pFileEntry);

// writes into the file local header - without Extra data
// puts the new offset to the file data to the file entry
// in case of error can put INVALID_DATA_OFFSET into the data offset field of file entry
extern ErrorEnum WriteLocalHeader (FILE*f, FileEntry* pFileEntry, const char* szRelativePath, bool encrypt);

// conversion routines for the date/time fields used in Zip
extern uint16 DOSDate(tm*);
extern uint16 DOSTime(tm*);

extern const char* DOSTimeCStr(uint16 nTime);
extern const char* DOSDateCStr(uint16 nTime);

struct DirHeader;
// this structure represents a subdirectory descriptor in the directory record.
// it points to the actual directory info (list of its subdirs and files), as well 
// as on its name
struct DirEntry
{
	uint32 nDirHeaderOffset; // offset, in bytes, relative to this object, of the actual directory record header
	uint32 nNameOffset; // offset of the dir name in the name pool of the parent directory

	// returns the name of this directory, given the pointer to the name pool of hte parent directory
	const char* GetName(const char* pNamePool)const
	{
		return pNamePool + nNameOffset;
	}

	// returns the pointer to the actual directory record. 
	// call this function only for the actual structure instance contained in a directory record and
	// followed by the other directory records
	const DirHeader* GetDirectory ()	const
	{
		return (const DirHeader*)(((const char*)this) + nDirHeaderOffset);
	}
	DirHeader* GetDirectory ()	
	{
		return (DirHeader*)(((char*)this) + nDirHeaderOffset);
	}
};

// this is the head of the directory record
// the name pool follows straight the directory and file entries.
struct DirHeader
{
	uint16 numDirs; // number of directory entries - DirEntry structures
	uint16 numFiles; // number of file entries - FileEntry structures

	// returns the pointer to the name pool that follows this object
	// you can only call this method for the structure instance actually followed by the dir record
	const char* GetNamePool()const
	{
		return ((char*)(this+1)) + (size_t)this->numDirs * sizeof(DirEntry) + (size_t)this->numFiles * sizeof(FileEntry);
	}
	char* GetNamePool()
	{
		return ((char*)(this+1)) + (size_t)this->numDirs * sizeof(DirEntry) + (size_t)this->numFiles * sizeof(FileEntry);
	}

	// returns the pointer to the i-th directory
	// call this only for the actual instance of the structure at the head of dir record
	const DirEntry* GetSubdirEntry(unsigned i)const
	{
		assert (i < numDirs);
		return ((const DirEntry*)(this+1)) + i;
	}
	DirEntry* GetSubdirEntry(unsigned i)
	{
		assert (i < numDirs);
		return ((DirEntry*)(this+1)) + i;
	}

	// returns the pointer to the i-th file
	// call this only for the actual instance of the structure at the head of dir record
	const FileEntry* GetFileEntry (unsigned i)const 
	{
		assert (i < numFiles);
		return (const FileEntry*)(((const DirEntry*)(this+1)) + numDirs) + i;
	}
	FileEntry* GetFileEntry (unsigned i)
	{
		assert (i < numFiles);
		return (FileEntry*)(((DirEntry*)(this+1)) + numDirs) + i;
	}

	// finds the subdirectory entry by the name, using the names from the name pool
	// assumes: all directories are sorted in alphabetical order.
	// case-sensitive (must be lower-case if case-insensitive search in Win32 is performed)
	DirEntry* FindSubdirEntry(const char* szName);

	// finds the file entry by the name, using the names from the name pool
	// assumes: all directories are sorted in alphabetical order.
	// case-sensitive (must be lower-case if case-insensitive search in Win32 is performed)
	FileEntry* FindFileEntry(const char* szName);

};

// this is the sorting predicate for directory entries
struct DirEntrySortPred
{
	DirEntrySortPred (const char* pNamePool):
		m_pNamePool (pNamePool)
		{
		}

	bool operator () (const FileEntry& left, const FileEntry& right)const
	{
		return strcmp(left.GetName(m_pNamePool), right.GetName(m_pNamePool)) < 0;
	}

	bool operator () (const FileEntry& left, const char* szRight)const
	{
		return strcmp(left.GetName(m_pNamePool), szRight) < 0;
	}

	bool operator () (const char* szLeft, const FileEntry& right)const
	{
		return strcmp(szLeft, right.GetName(m_pNamePool)) < 0;
	}

	bool operator () (const DirEntry& left, const DirEntry& right)const
	{
		return strcmp(left.GetName(m_pNamePool), right.GetName(m_pNamePool)) < 0;
	}

	bool operator () (const DirEntry& left, const char* szName)const
	{
		return strcmp(left.GetName(m_pNamePool), szName) < 0;
	}

	bool operator () (const char* szLeft, const DirEntry& right)const
	{
		return strcmp(szLeft, right.GetName(m_pNamePool)) < 0;
	}

	const char *m_pNamePool;
};

inline void tolower (string& str)
{
	str.MakeLower();
}

#if !defined(OPTIMIZED_READONLY_ZIP_ENTRY)
extern void Encrypt(char* buffer, size_t size);
extern void StreamCipher(char* buffer, size_t size, uint32 inKey);
extern void StreamCipher(char* buffer, const FileEntry *inEntry);
extern unsigned long GetStreamCipherKey(const FileEntry *inEntry);
#endif

extern void Decrypt(char* buffer, size_t size);

}

#endif
